#include <functional>
#include <future>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <signal.h>
#include <filesystem>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include "include/runcode.h"
#include "utility/configure.h"
#include "utility/fileio.h"
#include "utility/log.h"
#include "utility/options.h"
#include "languages/baseLang.h"
#include "judge_types/export_judge_type.hpp"
#include "judge_events/strategy.hpp"
#include "judge_events/event_context.hpp"
#include <sys/file.h>
using namespace std;

// 查询spjFile对应的语言类型，返回空字符串表示不支持该文件所描述的类型
std::string findSpjFileLangType(std::string spjFile) {
    Log::instance().getOutput() << "check file language:" << spjFile << endl;
    auto spjFilePath = std::filesystem::path(spjFile);
    string lastExtension, lastExtension2;
    if (spjFilePath.has_extension()) {
        lastExtension = spjFilePath.extension().string().substr(1);
    }
    if (spjFilePath.stem().has_extension()) {
        lastExtension2 = spjFilePath.stem().extension().string().substr(1);
    }

    auto langIt = langMap.find(lastExtension2);
    if (langIt != langMap.end()) {
        return lastExtension2;
    }

    auto suffixIt = suffixMap.find(lastExtension);
    if (suffixIt != suffixMap.end()) {
        return suffixIt->second;
    }

    return "";
}


std::ostream& operator<<(std::ostream& out, const RuncodeParams& rcps) {
    out << "RuncodeParams:" << endl;
    out << "language:	[" << rcps.language << "]" << endl;
    out << "judgeType:	[" << PROBLEM_TYPE::find_option_name(rcps.judgeType) << "]" << endl;
    out << "filename:	[" << rcps.filename << "]" << endl;
    out << "initPath:	[" << rcps.initPath << "]" << endl;
    out << "dataPath:	[" << rcps.dataPath << "]" << endl;
    out << "spjPath:	[" << rcps.spjPath << "]" << endl;
    out << "timeLimit:	[" << rcps.timeLimit << "]" << endl;
    out << "memoryLimit:[" << rcps.memoryLimit << "]" << endl;
    out << "compareFlag:[" << COMPARE_TYPE::find_multi_option_name(rcps.compareFlag) << "]" << endl;
    out << "ruler:		[" << CONTENT_TYPE::find_option_name(rcps.ruler) << "]" << endl;
    out << "querySet:   [" << QUERY_SET_TYPE::find_multi_option_name(rcps.querySet) << "]" << endl;
    out << "eps:		[" << rcps.eps << "]" << endl;
    out << "userCachePath:	[" << rcps.userCachePath << "]" << endl;
    out << "spjCachePath:	[" << rcps.spjCachePath << "]" << endl;
    return out;
}

std::ostream& operator<<(std::ostream& out, const CodeFileInfo& cfi) {
    out << "CodeFileInfo:" << endl;
    out << "lang:       [" << cfi.lang << "]" << endl;
    out << "codeFile:   [" << cfi.codeFile << "]" << endl;
    out << "binFile:    [" << cfi.binFile << "]" << endl;
    out << "compileStr: [" << cfi.compileStr << "]" << endl;
    out << "runStr:     [" << cfi.runStr << "]" << endl;
    return out;
}

using namespace run_code_event_space;

int parse_run_params(struct RuncodeParams& run_params, JsonObjectPtr data) {
    try {
        if_ex_set(run_params.language, data, "language");
        if_ex_set(run_params.judgeType, data, "judgeType");
        if_ex_set(run_params.filename, data, "filename");
        if_ex_set(run_params.initPath, data, "initPath");
        if_ex_set(run_params.dataPath, data, "dataPath");
        if_ex_set(run_params.spjPath, data, "spjPath");
        if_ex_set(run_params.timeLimit, data, "timeLimit");
        if_ex_set(run_params.memoryLimit, data, "memoryLimit");
        if_ex_set(run_params.compareFlag, data, "compareFlag");
        if_ex_set(run_params.ruler, data, "ruler");
        if_ex_set(run_params.eps, data, "eps");
        if_ex_set(run_params.userCachePath, data, "userCachePath");
        if_ex_set(run_params.spjCachePath, data, "spjCachePath");
        if_ex_set(run_params.querySet, data, "querySet");
        return 0;
    } catch (exception e) {
        Log::instance().getError() << e.what() << endl;
    }
    return 1;
}


template <class judge_type_t>
ProcessStatus execute_strategy(const RuncodeParams& params) {
    run_code_context_t<judge_type_t> context(params);
    auto res = context.init();
    if (res.result != RUN_OK) {
        Log::instance().getOutput() << "init context fail" << endl
                                    << res << endl;
        return res;
    }
    res = context.execute();
    if (res.result != RUN_OK) {
        Log::instance().getOutput() << "context execute fail" << endl
                                    << res << endl;
        return res;
    }
    auto get_jsonobj_from_optional = [](const std::optional<std::string>& opt) {
        return opt ? JSONOBJECT(opt.value()) : JSONOBJECT(nullptr);
    };
    // 整合结果
    res = context.runtime_info;
    res.extern_info = JSONDICTPTR({});
    JsonDict& result = res.extern_info->asDict();
    if (params.querySet & RETURN_FIRST_WRONG_OUTPUT) {
        result["first_wrong_output"] = get_jsonobj_from_optional(context.first_error_user_out);
    }
    if (params.querySet & RETURN_FIRST_WRONG_INPUT) {
        result["first_wrong_input"] = get_jsonobj_from_optional(context.first_error_example_in);
    }
    if (params.querySet & RETURN_FIRST_WRONG_STDOUT) {
        result["first_wrong_stdout"] = get_jsonobj_from_optional(context.first_error_std_out);
    }

    result["result_list"] = JSONARRAYPTR({});
    JsonArray& result_list = result["result_list"]->asArray();
    for (const auto& cs : context.all_case_status) {
        JsonDict tmp{};
        if (params.querySet & RETURN_ALL_RESULT) {
            tmp["time"] = JSONOBJECT(cs.time);
            tmp["memory"] = JSONOBJECT(cs.memory);
            tmp["result"] = JSONOBJECT(codeToStr[cs.result]);
            tmp["error"] = JSONOBJECT(cs.error);
        }
        if (params.querySet & RETURN_ALL_OUTPUT) {
            tmp["output"] = JSONOBJECT(cs.user_out);
        }
        if (params.querySet & RETURN_ALL_EXAMPLE_IN) {
            tmp["example_in"] = JSONOBJECT(cs.example_in);
        }
        if (params.querySet & RETURN_ALL_STDOUT) {
            tmp["stdout"] = get_jsonobj_from_optional(cs.std_out);
        }
        result_list.push_back(JSONOBJECT(tmp));
    }

    return res;
}

ProcessStatus runJudge(const RuncodeParams &params)
{
    switch (params.judgeType) {
        case NORMAL_JUDGE:
            return execute_strategy<NormalJudge>(params);
            break;
        case SPECIAL_JUDGE:
            return execute_strategy<SpecialJudge>(params);
            break;
        case INTERACTIVE_JUDGE:
            return execute_strategy<InteractiveJudge>(params);
            break;
        case MODULE_JUDGE:
            return execute_strategy<ModuleJudge>(params);
            break;
#ifdef ENABLE_DB_JUDGE
        case DATABASE_SELECT_JUDGE:
            return execute_strategy<DatabaseSelectJudge>(params);
            break;
        case DATABASE_MODIFY_JUDGE:
            return execute_strategy<DatabaseModifyJudge>(params);
            break;
#endif
        default:
            return ProcessStatus{
                            .result = UNKNOWN_JUDGE_TYPE,
                            .error = "unknown judge type"
                        };
    }
}

ProcessStatus runJudge( std::string language, 
                        int judgeType, 
                        std::string filename, 
                        std::string initPath,
                        std::string dataPath, 
                        std::string spjPath,
                        int timeLimit, 
                        int memoryLimit, 
                        int compareFlag, 
                        int ruler,
                        int querySet,
                        std::string eps,
                        std::string userCachePath,
                        std::string spjCachePath
                        ){
    return runJudge(RuncodeParams {
                        language,
                        judgeType,
                        filename,
                        initPath,
                        dataPath,
                        spjPath,
                        timeLimit,
                        memoryLimit,
                        compareFlag,
                        ruler,
                        querySet,
                        eps,
                        userCachePath,
                        spjCachePath,
                    });
}

#ifdef __linux__
ProcessStatus general_execution_strategy::twins_process_run_impl(
                    const Language* lang1, const string& command1, 
                    const Language* lang2, const string& command2, 
                    int timeLimit, int memoryLimit, 
                    const string& inFile, const string& outFile, 
                    const string& path1, const string& path2) {

    // command1作为spj进程,command2作为user进程
    ProcessStatus ps;

    // spj进程和user进程通信管道
    int spj2user[2];
    int user2spj[2];
    pipe(spj2user);
    pipe(user2spj);

    // spj进程和user进程输出错误管道
    int spjError[2];
    int userError[2];
    pipe(spjError);
    pipe(userError);

    // midSpjOut midUserOut表示对user和spj的输出内容进行分发
    // midSpjOut管道将连接一个新的线程用于复制输出内容给user进程
    // midUserOut管道将连接一个新的线程用于复制输出内容给spj进程
    int midSpjOut[2];
    int midUserOut[2];
    pipe(midSpjOut);
    pipe(midUserOut);

    // 这两个管道用于将数据拷贝给各自的监听进程
    int spj2watch[2];
    int user2watch[2];
    pipe(spj2watch);
    pipe(user2watch);

    if (spj2user[0] < 0 || user2spj[0] < 0 || spjError[0] < 0 || userError[0] < 0) {
        ps.result = PIPE_ERROR;
        ps.error = "create pipe error";
        return ps;
    }

    Log::instance().getOutput() << "dataFile:" << inFile << endl;
    
    // 先对spj进程输入标准输入样例
    std::ifstream in_data(inFile);
    defer(
        in_data.close();
    );
    int res;
    res = streamToFileno(in_data, user2spj[1]);
    if (res < 0) {
        ps.result = PIPE_ERROR;
        ps.error = "stream to fileno error";
        return ps;
    }
    Log::instance().getOutput() << "dataFile write count(byte):" << res << endl;
    // 如果in_data非空则保证中间隔一个换行
    if (res > 0) {
        in_data.clear();
        in_data.seekg(-1, in_data.end);
        if (in_data.get() != '\n') {
            Log::instance().getOutput() << "need new line" << endl;
            write(user2spj[1], "\n", 1);
        }
    }

    int outFileFd = -1;
    if (!outFile.empty()) {
        outFileFd = open(outFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0700);
        Log::instance().getOutput() << "outFileFd:" << outFileFd << endl;
        if (outFileFd < 0) {
            // 保证所有资源都关闭, 这个实现并不好,之后需要在完善
            close(spj2user[0]);
            close(user2spj[0]);
            close(spjError[0]);
            close(userError[0]);
            close(midSpjOut[0]);
            close(midUserOut[0]);
            close(spj2watch[0]);
            close(user2watch[0]);
            close(spj2user[1]);
            close(user2spj[1]);
            close(spjError[1]);
            close(userError[1]);
            close(midSpjOut[1]);
            close(midUserOut[1]);
            close(spj2watch[1]);
            close(user2watch[1]);
            return ProcessStatus{
                        .result = SYS_ERROR,
                        .error = strerror(errno)
            };
        }
    }
    Log::instance().getOutput() << "start mid user data forward thread" << endl;
    // read midUserOut[0] and write user2spj[1], user2watch[0], outFile
    auto midUserOutThread = std::async(std::launch::async, [=]() -> void {
        Log::instance().getOutput() << "midUserOut is running" << endl;
        if (outFileFd >= 0) {
            readAndMultiWriteUseLinuxFunc(midUserOut[0], outFileFd, user2spj[1], user2watch[1]);
            close(outFileFd);
        } else {
            readAndMultiWriteUseLinuxFunc(midUserOut[0], user2spj[1], user2watch[1]);
        }
        Log::instance().getOutput() << "midUserOut close all fd" << endl;
        close(midUserOut[0]);
        close(user2spj[1]);
        close(user2watch[1]);
    });
    Log::instance().getOutput() << "mid user data forward thread started" << endl;

    Log::instance().getOutput() << "start spj mid data forward thread" << endl;
    // 提前启动输出中转线程
    // read midSpjOut[0] and write spj2user[1], spj2watch[1]
    auto midSpjOutThread = std::async(std::launch::async, [=]() -> void {
        Log::instance().getOutput() << "midSpjOut is running" << endl;
        readAndMultiWriteUseLinuxFunc(midSpjOut[0], spj2user[1], spj2watch[1]);
        Log::instance().getOutput() << "midSpjOut close all fd" << endl;
        close(midSpjOut[0]);
        close(spj2user[1]);
        close(spj2watch[1]);
    });
    Log::instance().getOutput() << "mid spj data forward thread started" << endl;

    // 先启动spj进程,切记加入NO_PTRACE参数，不然无法捕获到输出
    printTimeSpanToOutAndError();
    auto spjId = createProcess_(command1,
                                timeLimit + 1000 + lang1->extra_time,
                                -1,
                                NO_PTRACE,
                                ProcessSTDNO(user2spj[0], midSpjOut[1], spjError[1]),
                                path1,
                                lang1->disableSysCallSet + SpecialJudge::disableSysCallSet);
    // 管道符已经传入子进程，关闭主进程的管道符
    close(user2spj[0]);
    close(midSpjOut[1]);
    close(spjError[1]);

    // 启动user进程
    printTimeSpanToOutAndError();
    auto userId = createProcess_(command2,
                                    timeLimit + lang2->extra_time,
                                    memoryLimit + lang2->extra_memory,
                                    0,
                                    ProcessSTDNO(spj2user[0], midUserOut[1], userError[1]),
                                    path2,
                                    lang2->disableSysCallSet + SpecialJudge::disableSysCallSet);
    // 管道符已经传入子进程，关闭主进程的管道符
    close(spj2user[0]);
    close(midUserOut[1]);
    close(userError[1]);
    
    Log::instance().getOutput() << "process id:" << spjId << " " << userId << endl;

    // 先监控userId进程,但必须后启动user process
    printTimeSpanToOutAndError();
    auto userPsFut = std::async(std::launch::deferred,
        [=]{
            auto res = WatchProcess(userId, 
            timeLimit, memoryLimit, 1, 
            ProcessSTDNO(-1, user2watch[0], userError[0]));

            // 关闭读端
            close(user2watch[0]);
            close(userError[0]);

            return res;
        }
    );

    // 启动spj process
    printTimeSpanToOutAndError();
    auto spjPsFut = std::async(std::launch::async,
        [=]{
            auto res = WatchProcess(spjId, 
            timeLimit + 1000, -1, 1, 
            ProcessSTDNO(-1, spj2watch[0], spjError[0]));

            // 关闭读端
            close(spj2watch[0]);
            close(spjError[0]);

            return res;
        }
    );

    // 先等待user process完成
    auto userPs = userPsFut.get();
    Log::instance().getOutput() << "user process: " << userId << " is done" << endl;

    auto spjPs = spjPsFut.get();
    Log::instance().getOutput() << "spj process: " << spjId << " is done" << endl;

    Log::instance().getOutput() << "close mid data forward threads" << endl;
    midSpjOutThread.wait();
    midUserOutThread.wait();


    Log::instance().getOutput() << "interactive userPs:" << userPs << endl;
    Log::instance().getOutput() << "interactive spjPs:" << spjPs << endl;
    if (userPs.result != RUN_OK) {
        return userPs;
    }
    if (spjPs.result != RUN_OK) {
        userPs.result = spjPs.result;
        userPs.error = spjPs.error;
        userPs.errorcode = spjPs.errorcode;
        return userPs;
    }
    return userPs;
}
#endif

#ifdef ENABLE_DB_JUDGE
ProcessStatus general_execution_strategy::database_update(MYSQL *conn, const string& database, const string& createSql, const string& spjCachePath) {
    int res;
    ProcessStatus ps;

    Log::instance().getOutput() << "createSql:" << createSql << endl;

    Log::instance().getError() << "connecting" << endl;
    auto& [host, port, root, passwd, user, user_passwd] = databaseParams;

    defer(
        mysql_refresh(conn, REFRESH_TABLES);
    );

    auto database_update_impl = [&]() {
        ProcessStatus local_ps;
        local_ps = create_database(conn, {user, user_passwd}, host, database, createSql);
        Log::instance().getOutput() << "create database result:" << endl << ps.json() << endl;
        if (ps.result != RUN_OK) {
            return local_ps;
        }
        mysql_commit(conn);
        return local_ps;
    };

    auto default_lang = langMap["default"];
    auto targetFile = default_lang->detect(spjCachePath, createSql);

    return general_execution_strategy::check_and_update(createSql, 
                            targetFile, 
                            database_update_impl, 
                            targetFile + ".check", 
#ifdef MULTI_PROCESS
                            targetFile + ".lock",
#endif
                            [&conn, database, createSql]{ return check_update_database(conn, database, createSql) > 0; }
                        );
}
#endif